package cn.net.xyan.easy.spring.web.support;

import cn.net.xyan.easy.spring.core.config.AbstractAutoConfig;
import cn.net.xyan.easy.spring.core.config.AutoConfigManagerFactory;
import cn.net.xyan.easy.spring.core.config.IAutoConfig;
import cn.net.xyan.easy.spring.core.config.IAutoConfigManager;
import cn.net.xyan.easy.spring.core.utils.JsonHelper;
import cn.net.xyan.easy.spring.web.config.MVCApplicationContextConfig;
import cn.net.xyan.easy.spring.web.config.RootApplicationContextConfig;
import org.springframework.core.Conventions;
import org.springframework.core.annotation.Order;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.util.Assert;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.*;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;

import static cn.net.xyan.easy.spring.core.config.AbstractAutoConfig.ApplicationContextEnum.*;

/**
 * Created by xiashenpin on 16/1/16.
 */
@Order(value = IAutoConfig.RootOrder)
public class RootInitalizer extends AbstractAutoConfig implements IWebApplicationInitializer {


    public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

    @Override
    protected void configManager() {
        registerApplicationConfigClass(ROOT,RootApplicationContextConfig.class);
        registerApplicationConfigClass(MVC,MVCApplicationContextConfig.class);
        putValue(IAutoConfigManager.MaxRequestSizeKey,52428800L);
        setupKey(IAutoConfigManager.ObjectMapperKey);
        setupKey(IAutoConfigManager.EnableSecuritySupportKey).putValue(Boolean.TRUE);
    }

    @Override
    protected void afterConfigManager() {

    }


    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();

        Set<Class> rootConfigCls = AutoConfigManagerFactory.autoConfigManager().readValue(IAutoConfigManager.RootApplicationContextConfigClassKey, JsonHelper.ClassSet);

        rootContext.register(rootConfigCls.toArray(new Class[0]));

        // Manage the lifecycle of the root application context
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();

        Set<Class> mvcConfigCls =  AutoConfigManagerFactory.autoConfigManager().readValue(IAutoConfigManager.MVCApplicationContextConfigClassKey,JsonHelper.ClassSet);

        dispatcherServlet.register(mvcConfigCls.toArray(new Class[0]));

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));

        long maxRequestSize = AutoConfigManagerFactory.autoConfigManager().readValue(IAutoConfigManager.MaxRequestSizeKey,Long.class);
        MultipartConfigElement multipartConfigElement = new MultipartConfigElement("/",maxRequestSize,maxRequestSize,0);
        dispatcher.setMultipartConfig(multipartConfigElement);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");



        IAutoConfigManager manager = AutoConfigManagerFactory.autoConfigManager();
        String entityManagerInViewFilterParam = manager.readValue(IAutoConfigManager.EntityManagerInViewFilterParamKey,String.class);
        if (entityManagerInViewFilterParam != null){
            OpenEntityManagerInViewFilter entityManagerInViewFilter =  new OpenEntityManagerInViewFilter();

            entityManagerInViewFilter.setEntityManagerFactoryBeanName(entityManagerInViewFilterParam);

            registerFilter(servletContext,true,"OpenEntityManagerInViewFilter",entityManagerInViewFilter);
        }




        if (manager.readValue(IAutoConfigManager.EnableSecuritySupportKey,Boolean.class)){
            servletContext
                    .addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
            servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
            insertSpringSecurityFilterChain(servletContext);
        }

    }

    /**
     * Registers the springSecurityFilterChain
     * @param servletContext the {@link ServletContext}
     */
    private void insertSpringSecurityFilterChain(ServletContext servletContext) {
        String filterName = DEFAULT_FILTER_NAME;
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
                filterName);
//        String contextAttribute = getWebApplicationContextAttribute();
//        if (contextAttribute != null) {
//            springSecurityFilterChain.setContextAttribute(contextAttribute);
//        }
        registerFilter(servletContext, true, filterName, springSecurityFilterChain);
    }



    /**
     * Registers the provided {@link Filter}s using default generated names,
     * {@link #getSecurityDispatcherTypes()}, and {@link #isAsyncSecuritySupported()}.
     *
     * @param servletContext the {@link ServletContext} to use
     * @param insertBeforeOtherFilters if true, will insert the provided {@link Filter}s
     * before other {@link Filter}s. Otherwise, will insert the {@link Filter}s after
     * other {@link Filter}s.
     * @param filters the {@link Filter}s to register
     */
    private void registerFilters(ServletContext servletContext,
                                 boolean insertBeforeOtherFilters, Filter... filters) {
        Assert.notEmpty(filters, "filters cannot be null or empty");

        for (Filter filter : filters) {
            if (filter == null) {
                throw new IllegalArgumentException(
                        "filters cannot contain null values. Got "
                                + Arrays.asList(filters));
            }
            String filterName = Conventions.getVariableName(filter);
            registerFilter(servletContext, insertBeforeOtherFilters, filterName, filter);
        }
    }

    /**
     * Registers the provided filter using the {@link #isAsyncSecuritySupported()} and
     * {@link #getSecurityDispatcherTypes()}.
     *
     * @param servletContext
     * @param insertBeforeOtherFilters should this Filter be inserted before or after
     * other {@link Filter}
     * @param filterName
     * @param filter
     */
    private final void registerFilter(ServletContext servletContext,
                                      boolean insertBeforeOtherFilters, String filterName, Filter filter) {
        FilterRegistration.Dynamic registration = servletContext.addFilter(filterName, filter);
        if (registration == null) {
            throw new IllegalStateException("Duplicate Filter registration for '"
                    + filterName
                    + "'. Check to ensure the Filter is only configured once.");
        }
        registration.setAsyncSupported(isAsyncSecuritySupported());
        EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
        registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
                "/*");
    }

    /**
     * Get the {@link DispatcherType} for the springSecurityFilterChain.
     * @return
     */
    protected EnumSet<DispatcherType> getSecurityDispatcherTypes() {
        return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR,
                DispatcherType.ASYNC);
    }

    /**
     * Determine if the springSecurityFilterChain should be marked as supporting asynch.
     * Default is true.
     *
     * @return true if springSecurityFilterChain should be marked as supporting asynch
     */
    protected boolean isAsyncSecuritySupported() {
        return true;
    }
}
